Passed
Push — master ( e88902...5a4dfd )
by Apocist
43s
created

nodeVBulletinAPI.js ➔ getMessage   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 2
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
1
'use strict';
2
const md5 = require('js-md5'),
3
    os = require('os'),
4
    request = require('request'),
5
    _ = require('underscore'),
6
    Forum = require('./Forum'),
7
    Inbox = require('./Inbox'),
8
    Member = require('./Member'),
9
    Message = require('./Message'),
10
    Post = require('./Post'),
11
    Thread = require('./Thread'),
12
    {version} = require('./package.json');
13
14
/**
15
 *
16
 */
17
class VBApi {
18
    /**
19
     * Initialize a vb api connection .This needs to be called for the first time
20
     * @param {string} apiUrl
21
     * @param {string} apiKey
22
     * @param {string} platformName
23
     * @param {string} platformVersion
24
     * @param {object=} options - A fallback to the old style config
25
     * @param {string=} options.apiUrl
26
     * @param {string=} options.apiKey
27
     * @param {string=} options.platformName
28
     * @param {string=} options.platformVersion
29
     */
30
    constructor(apiUrl, apiKey, platformName, platformVersion, options) {
31
        this.defaultVars = {
32
            baseUrl: '', //Needed for cookie related commands
33
            apiUrl: '',
34
            apiKey: '',
35
            clientName: 'nodeVBulletinAPI',
36
            clientVersion: version,
37
            uniqueId: ''
38
        };
39
40
        this.clientSessionVars = {
41
            apiVersion: '',
42
            apiAccessToken: '',
43
            sessionHash: '', // Unused?
44
            apiClientId: '',
45
            secret: '',
46
            inited: false,
47
            error: null
48
        };
49
50
        /**
51
         * @typedef UserVars
52
         * @property {string} dbsessionhash
53
         * @property {number} userid
54
         * @property {string} username
55
         * @property {boolean} loggedIn
56
         * @type {UserVars}
57
         */
58
        this.userSessionVars = {
59
            dbsessionhash: '',
60
            username: '',
61
            userid: 0,
62
            loggedIn: false
63
        };
64
65
        /** @private */
66
        this.__waitingForInitializationCallback = function () {
67
        }; // A blank callback to be filled in
68
69
        options = options || {};
70
        options.apiUrl = apiUrl || options.apiUrl || '';
71
        options.apiKey = apiKey || options.apiKey || '';
72
        options.platformName = platformName || options.platformName || '';
73
        options.platformVersion = platformVersion || options.platformVersion || '';
74
75
        if (
76
            options.apiUrl !== ''
77
            && options.apiUrl !== ''
78
            && options.platformName !== ''
79
            && options.platformVersion !== ''
80
        ) {
81
            this.__initialize(options);
82
        } else {
83
            this.clientSessionVars.error = 'apiInit(): Initialization requires a `apiUrl`, `apiKey`, `platformName`, and `platformVersion`';
84
            this.__waitingForInitializationCallback(false);
85
        }
86
    }
87
88
    /**
89
     * Initialize a vb api connection. This needs to be called for the first time
90
     * @param {object} options
91
     * @param {string} options.apiUrl
92
     * @param {string} options.apiKey
93
     * @param {string} options.platformName
94
     * @param {string} options.platformVersion
95
     * @private
96
     */
97
    __initialize(options) {
98
        let that = this;
99
        // Run itself as a self invoked promise that is awaited by nothing. callMethod shall wait until this is finished
100
        (async function __initialize_self() {
101
            let error = null;
102
            let result = null;
103
            let regex_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
104
            let url_parts = regex_url.exec(options.apiUrl);
105
            that.defaultVars.baseUrl = that.defaultVars.baseUrl || url_parts[1] + ':' + url_parts[2] + url_parts[3] + '/';
106
            that.defaultVars.apiUrl = that.defaultVars.apiUrl || options.apiUrl;
107
            that.defaultVars.apiKey = that.defaultVars.apiKey || options.apiKey;
108
            that.defaultVars.uniqueId = that.defaultVars.uniqueId || md5(that.defaultVars.clientName + that.defaultVars.clientVersion + options.platformName + options.platformVersion + that.constructor.getMacAddress() + new Date().getTime());
109
110
            try {
111
                /**
112
                 *
113
                 * @type {{}}
114
                 * @property {string} apiversion
115
                 * @property {string} apiaccesstoken
116
                 * @property {string} sessionhash
117
                 * @property {string} apiclientid
118
                 * @property {string} secret
119
                 */
120
                let response = await that.callMethod({
121
                    method: 'api_init',
122
                    params: {
123
                        clientname: that.defaultVars.clientName,
124
                        clientversion: that.defaultVars.clientVersion,
125
                        platformname: options.platformName,
126
                        platformversion: options.platformVersion,
127
                        uniqueid: that.defaultVars.uniqueId
128
                    }
129
                });
130
131
                that.clientSessionVars.apiVersion = '';
132
                that.clientSessionVars.apiAccessToken = '';
133
                that.clientSessionVars.sessionHash = '';
134
                that.clientSessionVars.apiClientId = '';
135
                that.clientSessionVars.secret = '';
136
                that.clientSessionVars.inited = false;
137
                if (
138
                    response.apiversion
139
                    && response.apiaccesstoken
140
                    && response.sessionhash
141
                    && response.apiclientid
142
                    && response.secret
143
                ) {
144
                    that.clientSessionVars.apiVersion = response.apiversion;
145
                    that.clientSessionVars.apiAccessToken = response.apiaccesstoken;
146
                    that.clientSessionVars.sessionHash = response.sessionhash;
147
                    that.clientSessionVars.apiClientId = response.apiclientid;
148
                    that.clientSessionVars.secret = response.secret;
149
                    that.clientSessionVars.inited = true;
150
                    that.__waitingForInitializationCallback(true);
151
                    result = that;
152
                }
153
154
                if (result === null) {
155
                    that.clientSessionVars.error = that.constructor.parseErrorMessage(response) || 'TODO ERROR (api connection did not return a session)';
156
                    that.__waitingForInitializationCallback(false);
157
                    error = that.clientSessionVars.error;
158
                }
159
            } catch (e) {
160
                that.clientSessionVars.error = e;
161
                that.__waitingForInitializationCallback(false);
162
                // reject(e);
163
                error = e;
164
            }
165
            return error || result;
166
        }());
167
    }
168
169
    /**
170
     * Will return after #initialize() is complete. Otherwise may reject() after 15 second timeout
171
     * @param {number=5} waitTime
172
     * @returns {Promise<void>}
173
     * @fulfill {void}
174
     * @reject {string} - Error Reason
175
     */
176
    async waitForInitialization(waitTime) {
177
        let that = this;
178
        waitTime = waitTime || 5;
179
        return new Promise(async function (resolve, reject) {
180
            if (that.clientSessionVars.inited === true) {
181
                resolve();
182
            } else if (that.clientSessionVars.error !== null) {
183
                reject(that.clientSessionVars.error);
184
            } else {
185
                /**
186
                 * @type {number}
187
                 * @private
188
                 */
189
                that.__waitingForInitializationTimeout = setTimeout(
190
                    function () {
191
                        that.__waitingForInitializationCallback = function () {
192
                        }; // Set back to a blank function
193
                        if (that.clientSessionVars.inited === true) {
194
                            resolve();
195
                        } else {
196
                            reject('Connection could not be achieved due to timed out', that.clientSessionVars.error);
197
                        }
198
199
                    },
200
                    waitTime * 1000 // x second timeout
201
                );
202
                /**
203
                 * @param {boolean=true} success
204
                 * @private
205
                 */
206
                that.__waitingForInitializationCallback = function (success) {
207
                    if (that.__waitingForInitializationTimeout) {
208
                        clearTimeout(that.__waitingForInitializationTimeout);
209
                    }
210
                    if (success === false) {
211
                        reject(that.clientSessionVars.error);
212
                    } else {
213
                        resolve();
214
                    }
215
                };
216
            }
217
        })
218
    }
219
220
    /**
221
     *
222
     * @param {object} options
223
     * @param {string} options.method - Required action to take
224
     * @param {object<string,string>} [options.params={}] - Optional parameter variables
225
     * @param {?object<string,string>} [options.cookies] - Optional cookie variables
226
     * @returns {Promise<{}>}
227
     * @fulfill {{}}
228
     * @reject {string} - Error Reason
229
     */
230
    async callMethod(options) {
231
        let that = this;
232
        let sign = true;
233
        options = options || {};
234
        options.params = options.params || {};
235
        return new Promise(async function (resolve, reject) {
236
            try {
237
                if (!options.method) {
238
                    reject('callMethod(): requires a supplied method');
239
                    return;
240
                }
241
242
                // Sign all calls except for api_init
243
                if (options.method === 'api_init') {
244
                    sign = false;
245
                }
246
247
                // await a valid session before continuing (skipping waiting on __initialize())
248
                if (sign === true) {
249
                    await that.waitForInitialization();
250
                }
251
252
                // Gather our sessions variables together
253
                let reqParams = {
254
                    api_m: options.method,
255
                    api_c: that.clientSessionVars.apiClientId, //clientId
256
                    api_s: that.clientSessionVars.apiAccessToken, //apiAccessToken (may be empty)
257
                    api_v: that.clientSessionVars.apiVersion //api version
258
                };
259
                _.extend(reqParams, options.params); // Combine the arrays
260
261
                if (sign === true) {
262
                    // Generate a signature to validate that we are authenticated
263
                    if (that.clientSessionVars.inited) {
264
                        reqParams.api_sig = md5(that.clientSessionVars.apiAccessToken + that.clientSessionVars.apiClientId + that.clientSessionVars.secret + that.defaultVars.apiKey);
265
                    } else {
266
                        reject('callMethod(): requires initialization. Not initialized');
267
                        return;
268
                    }
269
                }
270
271
                // Create a valid http Request
272
                let reqOptions = {
273
                    url: that.defaultVars.apiUrl,
274
                    formData: reqParams,
275
                    headers: {
276
                        'User-Agent': that.defaultVars.clientName
277
                    }
278
                };
279
280
                // Some command require adding a cookie, we'll do that here
281
                if (options.cookies) {
282
                    let j = request.jar();
283
                    for (let variable in options.cookies) {
284
                        if (options.cookies.hasOwnProperty(variable)) {
285
                            let cookieString = variable + '=' + options.cookies[variable];
286
                            let cookie = request.cookie(cookieString);
287
                            j.setCookie(cookie, that.defaultVars.baseUrl);
288
                        }
289
                    }
290
                    reqOptions.jar = j;// Adds cookies to the request
291
                }
292
293
                request.post(
294
                    reqOptions,
295
                    function (error, response, body) {
296
                        if (!error && response.statusCode === 200) {
297
                            resolve(JSON.parse(body));
298
                        } else {
299
                            //console.log('No response');
300
                            reject('callMethod(): no response.');
301
                        }
302
                    }
303
                );
304
            } catch (e) {
305
                reject(e);
306
            }
307
        });
308
    }
309
310
    /**
311
     * Attempts to log in a user.
312
     * @param {string} username - Username
313
     * @param {string} password - clear text password TODO need to secure this more?
314
     * @param {object=} options
315
     * @param {string=} options.username - Ignore, already required at username
316
     * @param {string=} options.password - Ignore, already required at password
317
     * @returns {Promise<UserVars>}
318
     * @fulfill {UserVars}
319
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
320
     */
321
    async login(username, password, options) {
322
        options = options || {};
323
        options.username = username || options.username || '';
324
        options.password = md5(password || options.password || '');
325
        return await this.loginMD5('', '', options);
326
    }
327
328
    /**
329
     *
330
     * Attempts to log in a user. Requires the password to be pre md5 hashed.
331
     * @param {string} username - Username
332
     * @param {string} password - MD5 hashed password TODO need to secure this more?
333
     * @param {object=} options
334
     * @param {string=} options.username - Ignore, already required at username
335
     * @param {string=} options.password - Ignore, already required at password
336
     * @returns {Promise<UserVars>}
337
     * @fulfill {UserVars}
338
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
339
     */
340
    async loginMD5(username, password, options) {
341
        let that = this;
342
        options = options || {};
343
        options.username = username || options.username || {};
344
        options.password = password || options.password || {};
345
        return new Promise(async function (resolve, reject) {
346
            try {
347
                let response = await that.callMethod(
348
                    {
349
                        method: 'login_login',
350
                        params: {
351
                            vb_login_username: options.username || '',
352
                            vb_login_md5password: options.password || ''
353
                        }
354
                    }
355
                );
356
                /**
357
                 redirect_login - (NOT A ERROR) Login successful
358
                 badlogin - Username or Password incorrect. Login failed.
359
                 badlogin_strikes - Username or Password incorrect. Login failed. You have used {X} out of 5 login attempts. After all 5 have been used, you will be unable to login for 15 minutes.
360
                 */
361
                let error = that.constructor.parseErrorMessage(response);
362
                if (response.session) {
363
                    that.userSessionVars = response.session;
364
                    if (error === 'redirect_login') {
365
                        that.userSessionVars.username = options.username;
366
                        that.userSessionVars.loggedIn = true;
367
                    }
368
                }
369
                if (error === 'redirect_login') {
370
                    error = null;
371
                }
372
                if (error === null) {
373
                    resolve(that.userSessionVars);
374
                } else {
375
                    reject(error);
376
                }
377
378
            } catch (e) {
379
                reject(e);
380
            }
381
        });
382
    }
383
384
    /**
385
     * Attempts to log the user out.
386
     * @returns {Promise<boolean>} - Returns true on success, otherwise error code is rejected
387
     * @fulfill {boolean}
388
     * @reject {string} - Error Reason
389
     */
390
    async logout() {
391
        let that = this;
392
        return new Promise(async function (resolve, reject) {
393
            let error;
394
            try {
395
                let response = await that.callMethod({
396
                    method: 'login_logout'
397
                });
398
                error = that.constructor.parseErrorMessage(response);
399
                if (response.session) {
400
                    that.userSessionVars = response.session;
401
                    if (error === 'cookieclear') {
402
                        that.userSessionVars.username = '';
403
                        that.userSessionVars.loggedIn = false;
404
                    }
405
                }
406
                if (error === 'cookieclear') {
407
                    error = null;
408
                }
409
            } catch (e) {
410
                reject(e);
411
            }
412
413
            if (error) {
414
                reject(error);
415
            } else {
416
                resolve(true)
417
            }
418
        });
419
    }
420
421
    /**
422
     * Return a Mac address of a network interface for machine identification
423
     * @returns {string} macAddress
424
     */
425
    static getMacAddress() {
426
        let interfaces = os.networkInterfaces();
427
        let address = '';
428
        loop1:
429
            for (let k in interfaces) {
430
                if (interfaces.hasOwnProperty(k)) {
431
                    for (let k2 in interfaces[k]) {
432
                        if (interfaces[k].hasOwnProperty(k2)) {
433
                            let addressI = interfaces[k][k2];
434
                            if (
435
                                (addressI.family === 'IPv4' || addressI.family === 'IPv6')
436
                                && addressI.hasOwnProperty('internal')
437
                                && addressI.internal === false
438
                                && addressI.hasOwnProperty('mac')
439
                                && addressI.mac !== '00:00:00:00:00:00'
440
                            ) {
441
                                address = addressI.mac;
442
                                break loop1;
443
                            }
444
                        }
445
                    }
446
                }
447
            }
448
        return address;
449
    }
450
451
    /**
452
     *
453
     * @param {object} response - Response object from callMethod()
454
     * @returns {string || null} status - Error message
455
     */
456
    static parseErrorMessage(response) {
457
        let retur = '';
458
        if (
459
            response.hasOwnProperty('response')
460
            && response.response.hasOwnProperty('errormessage')
461
        ) {
462
            if (_.isArray(response.response.errormessage)) {
463
                retur = response.response.errormessage[0]
464
            } else {
465
                retur = response.response.errormessage;
466
            }
467
        }
468
        return retur;
469
    }
470
471
    /**
472
     * List every Forum and sub forum available to the user.
473
     * @returns {Promise<Forum[]>} - Array of Forum objects
474
     * @fulfill {Forum[]}
475
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
476
     */
477
    getForums() {
478
        return Forum.getForums(this);
479
    }
480
481
    /**
482
     * List detailed info about a forum and it's sub-forums and threads
483
     * @param {number} forumId - Forum id
484
     * @param {object=} options - Secondary Options
485
     * @param {number=} options.forumid - Ignore, already required at forumId
486
     * TODO note additional options
487
     * @returns {Promise<Forum>} - Returns a Forum object
488
     * @fulfill {Forum}
489
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
490
     */
491
    getForum(forumId, options) {
492
        return Forum.getForum(this, forumId, options);
493
    }
494
495
496
    /**
497
     * Attempts to submit a new Post into a specified Thread
498
     * @param {number} threadId - Thread id
499
     * @param {string} message - Post Message
500
     * @param {object=} options
501
     * @param {boolean=} options.signature  - Optionally append your signature
502
     * @param {number=} options.threadid - Ignore, already required at threadId
503
     * @param {string=} options.message - Ignore, already required at message
504
     * TODO note additional options
505
     * @returns {Promise<*>} - Returns a unhandled response currently
506
     * @fulfill {*}
507
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
508
     */
509
    newPost(threadId, message, options) {
510
        return Post.newPost(this, threadId, message, options);
511
    }
512
513
    /**
514
     * Attempts to edit an existing Post
515
     * @param {number} postId - Post id
516
     * @param {string} message - Post Message
517
     * @param {object=} options
518
     * @param {string=} options.reason - Reason for editing
519
     * @param {boolean=} options.signature - Optionally append your signature
520
     * @param {number=} options.postid - Ignore, already required at postId
521
     * @param {string=} options.message - Ignore, already required at message
522
     * TODO note additional options
523
     * @returns {Promise<*>} - Returns a unhandled response currently
524
     * @fulfill {*}
525
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
526
     */
527
    editPost(postId, message, options) {
528
        return Post.editPost(this, postId, message, options);
529
    }
530
531
    /**
532
     * TODO untested - does not seem to function yet
533
     * Attempts to delete an existing Post
534
     * @param {number} postId - Post id
535
     * @param {number} threadId - Thread id
536
     * @param {object=} options
537
     * @param {string=} options.reason - Reason for deleting
538
     * @param {number=} options.postid - Ignore, already required at postId
539
     * @param {number=} options.threadid - Ignore, already required at threadId
540
     * TODO note additional options
541
     * @returns {Promise<*>} - Returns a unhandled response currently
542
     * @fulfill {*}
543
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
544
     */
545
    deletePost(postId, threadId, options) {
546
        return Post.deletePost(this, postId, threadId, options);
547
    }
548
549
    /**
550
     * List detailed information about a Thread and it's Posts
551
     * @param {number} threadId - Thread id
552
     * @param {object=} options - Secondary Options
553
     * @param {number=} options.threadid - Ignore, already required at threadId
554
     * TODO note additional options
555
     * @returns {Promise<Thread>} - Returns a Thread object
556
     * @fulfill {Thread}
557
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
558
     */
559
    getThread(threadId, options) {
560
        return Thread.getThread(this, threadId, options);
561
    }
562
563
    /**
564
     * Attempts to submit a new Thread into a specified Forum. This will also be considered the first Post
565
     * @param {number} forumId - Forum Id
566
     * @param {string} subject - Post/Thread Subject
567
     * @param {string} message - Post Message
568
     * @param {object=} options
569
     * @param {boolean=} options.signature - Optionally append your signature
570
     * @param {number=} options.forumid - Ignore, already required at postId
571
     * @param {string=} options.subject - Ignore, already required at postId
572
     * @param {string=} options.message - Ignore, already required at postId
573
     * TODO note additional options
574
     * @returns {Promise<*>} - Returns a unhandled response currently
575
     * @fulfill {*}
576
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
577
     */
578
    newThread(forumId, subject, message, options) {
579
        return Thread.newThread(this, forumId, subject, message, options);
580
    }
581
582
    /**
583
     * TODO incomplete - does not seem to function yet
584
     * Attempts to close a specific Thread. Requires a user to have a 'inline mod' permissions
585
     * @param {number} threadId - Id of Thread to close
586
     * @returns {Promise<*>} - Returns a unhandled response currently
587
     * @fulfill {*}
588
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
589
     */
590
    closeThread(threadId) {
591
        return Thread.closeThread(this, threadId);
592
    }
593
594
    /**
595
     * TODO incomplete - does not seem to function yet
596
     * Attempts to open a specific Thread. Requires a user to have a 'inline mod' permissions
597
     * @param {number} threadId - Id of Thread to open
598
     * @returns {Promise<*>} - Returns a unhandled response currently
599
     * @fulfill {*}
600
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
601
     */
602
    openThread(threadId) {
603
        return Thread.openThread(this, threadId);
604
    }
605
606
    /**
607
     * TODO incomplete - does not seem to function yet
608
     * Attempts to delete a specific Thread. Requires a user to have a 'inline mod' permissions
609
     * @param {number} threadId - Id of Thread to close
610
     * @returns {Promise<*>} - Returns a unhandled response currently
611
     * @fulfill {*}
612
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
613
     */
614
    deleteThread(threadId) {
615
        return Thread.deleteThread(this, threadId);
616
    }
617
618
    /**
619
     * @deprecated as of 1.3.1
620
     * @see closeThread
621
     */
622
    modCloseThread(threadId) {
623
        return Thread.closeThread(this, threadId);
624
    }
625
626
    /**
627
     * @deprecated as of 1.3.1
628
     * @see openThread
629
     */
630
    modOpenThread(threadId) {
631
        return Thread.openThread(this, threadId);
632
    }
633
634
    /**
635
     * @deprecated as of 1.3.1
636
     * @see deleteThread
637
     */
638
    modDeleteThread(threadId) {
639
        return Thread.deleteThread(this, threadId);
640
    }
641
642
    /**
643
     * Get logged in user's Inbox and list of private Messages
644
     * @param {object=} options
645
     * @returns {Promise<Inbox>} - Returns an Inbox object
646
     * @fulfill {Inbox}
647
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
648
     */
649
    getInbox(options) {
650
        return Inbox.getInbox(this, options);
651
    }
652
653
    /**
654
     * Attempts to submit a new Thread into a specified Forum. This will also be considered the first Post
655
     * @param {Date} date - Delete all messages from before the specified date
656
     * @param {number=0} folderId - Folder Id, defaults to 0
657
     * @param {object=} options
658
     * @param {string=} options.dateline - Ignore, already required at date
659
     * @param {number=} options.folderid - Ignore, already required at folderId
660
     * TODO note additional options
661
     * @returns {Promise<void>} - Returns a unhandled response currently
662
     * @fulfill {void}
663
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
664
     */
665
    emptyInbox(date, folderId, options) {
666
        return Inbox.emptyInbox(this, date, folderId, options)
667
    }
668
669
    /**
670
     * Get details of a specific Message for the logged in user
671
     * @param {number} id
672
     * @param {object=} options
673
     * @param {number=} options.pmid - Ignore, already required at id
674
     * @returns {Promise<Message>} - Returns a Message object
675
     * @fulfill {Message}
676
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
677
     */
678
    getMessage(id, options) {
679
        return Message.getMessage(this, id, options);
680
    }
681
682
    /**
683
     *
684
     * @param {string} username - Username to send the message to
685
     * @param {string} title - Message Subject
686
     * @param {string} message - Message content
687
     * @param {object=} options
688
     * @param {boolean=} options.signature - Optionally append your signature
689
     * @param {string=} options.recipients - Ignore, already required at username
690
     * @param {string=} options.title - Ignore, already required at title
691
     * @param {string=} options.message - Ignore, already required at message
692
     * TODO note additional options
693
     * @returns {Promise<void>} - Successfully completes if sent. TODO: provide a better response
694
     * @fulfill {void}
695
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
696
     */
697
    sendMessage(username, title, message, options) {
698
        return Message.sendMessage(this, username, title, message, options)
699
    }
700
701
    /**
702
     * Attempts to retrieve data about a specific user found by username
703
     * @param {string} username - Username
704
     * @param {object=} options - Secondary Options
705
     * @param {string=} options.username - Ignore, already required at username
706
     * @returns {Promise<Member>} - Returns a Member object
707
     * @fulfill {Member}
708
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
709
     */
710
    getMember(username, options) {
711
        return Member.getMember(this, username, options);
712
    }
713
}
714
715
module.exports = VBApi;
716